#include <bsl_sal.h>
#include <bsl_err.h>
#include <bsl_log.h>

#include <crypt_eal_init.h>
#include <crypt_algid.h>
#include <crypt_eal_rand.h>
#include <crypt_errno.h>
#include <crypt_provider.h>
#include <crypt_util_rand.h>

#include <hitls_error.h>
#include <hitls_config.h>
#include <hitls.h>
#include <hitls_cert_init.h>
#include <hitls_cert.h>
#include <hitls_crypt_init.h>
#include <hitls_pki_cert.h>
#include <hitls_x509_adapt.h>
#include <hitls_cert_init.h>
#include <hitls_cert_reg.h>
#include <hitls_pki_x509.h>
#include <cert_method.h>

#include <unistd.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <securec.h>
#include <setjmp.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include <cmocka.h>

#include "helper.h"
#include <kaep_err.h>

#include <pthread.h>

#define HTTP_BUF_MAXLEN (18 * 1024)

#define CA_PATH         "../../platform/openhitls/testcode/testdata/tls/certificate/der/rsa_sha256/ca.der"
#define CHAIN_PATH      "../../platform/openhitls/testcode/testdata/tls/certificate/der/rsa_sha256/inter.der"
#define EE_PATH      "../../platform/openhitls/testcode/testdata/tls/certificate/der/rsa_sha256/server.der"
#define PRIV_PATH    "../../platform/openhitls/testcode/testdata/tls/certificate/der/rsa_sha256/server.key.der"

static void *StdMalloc(uint32_t len)
{
    return malloc((uint32_t)len);
}

static void StdFree(void *addr)
{
    free(addr);
}

void PrintLastError(void) {
    const char *file = NULL;
    uint32_t line = 0;
    int32_t ret = 0;
    do {
        BSL_ERR_GetLastErrorFileLine(&file, &line);
        ret = BSL_ERR_GetLastError();
        printf("failed at file %s at line %d %d\n", file, line, ret);
    } while (ret != 0);
}

void BinLogFixLenFunc(uint32_t logId, uint32_t logLevel, uint32_t logType, void *format, void *para1, void *para2,
                      void *para3, void *para4)
{
    (void)logLevel;
    (void)logType;
    printf("logId:%u\t", logId);
    printf(format, para1, para2, para3, para4);
    printf("\n");
}

void BinLogVarLenFunc(uint32_t logId, uint32_t logLevel, uint32_t logType, void *format, void *para)
{
    (void)logLevel;
    (void)logType;
    printf("logId:%u\t", logId);
    printf(format, para);
    printf("\n");
}

void TLS13_SignServer()
{
    BSL_SAL_CallBack_Ctrl(BSL_SAL_MEM_MALLOC, StdMalloc);
    BSL_SAL_CallBack_Ctrl(BSL_SAL_MEM_FREE, StdFree);
    BSL_ERR_Init();
    CRYPT_EAL_RandInit(CRYPT_RAND_SHA256, NULL, NULL, NULL, 0);
    CRYPT_EAL_ProviderRandInitCtx(NULL, CRYPT_RAND_SHA256, "provider=default", NULL, 0, NULL);

    BSL_LOG_BinLogFuncs func = {0};
    func.fixLenFunc = BinLogFixLenFunc;
    func.varLenFunc = BinLogVarLenFunc;
    BSL_LOG_RegBinLogFunc(&func);

    CRYPT_EAL_LibCtx *libCtx = NULL;
    CRYPT_EAL_ProvMgrCtx *providerMgr = NULL;
    HITLS_Config *config = NULL;
    int32_t ret = CRYPT_SUCCESS;

    ret = CRYPT_EAL_Init(CRYPT_EAL_INIT_CPU);
    if (ret != CRYPT_SUCCESS) {
        printf("CRYPT_EAL_LibCtxNew failed, error code: %d.\n", ret);
        goto EXIT;
    }

    libCtx = CRYPT_EAL_LibCtxNew();
    if (libCtx == NULL) {
        printf("CRYPT_EAL_LibCtxNew failed.\n");
        goto EXIT;
    }

    ret = CRYPT_EAL_ProviderSetLoadPath(libCtx, ".");
    if (ret != CRYPT_SUCCESS) {
        printf("CRYPT_EAL_ProviderSetLoadPath failed, error code: %d.\n", ret);
        goto EXIT;
    }

    ret = CRYPT_EAL_ProviderLoad(libCtx, BSL_SAL_LIB_FMT_LIBSO, "tls_provider", NULL, &providerMgr);
    if (ret != CRYPT_SUCCESS || providerMgr == NULL) {
        printf("CRYPT_EAL_ProviderLoad failed, error code: %d.\n", ret);
        goto EXIT;
    }

    ret = CRYPT_EAL_ProviderLoad(libCtx, BSL_SAL_LIB_FMT_OFF, "default", NULL, NULL);
    if (ret != CRYPT_SUCCESS) {
        printf("CRYPT_EAL_ProviderLoad failed, error code: %d.\n", ret);
        goto EXIT;
    }

    ret = CRYPT_EAL_ProviderRandInitCtx(libCtx, CRYPT_RAND_SHA256, "provider=tls_provider", NULL, 0, NULL);
    if (ret != CRYPT_SUCCESS) {
        printf("CRYPT_EAL_ProviderRandInitCtx failed, error code: %d.\n", ret);
        goto EXIT;
    }

    config = HITLS_CFG_ProviderNewTLS13Config(libCtx, NULL);
    if (config == NULL) {
        PrintLastError();
        printf("HITLS_CFG_ProviderNewTLS13Config failed.\n");
        goto EXIT;
    }

    HITLS_CFG_SetCheckKeyUsage(config, false);
    HITLS_CFG_SetSecurityLevel(config, HITLS_SECURITY_LEVEL_ZERO);
    HITLS_CFG_SetReadAhead(config, 1);
    HITLS_CFG_SetFlightTransmitSwitch(config, false);

    uint16_t signScheme = 23333;
    ret = HITLS_CFG_SetSignature(config, &signScheme, 1);
    if (ret != HITLS_SUCCESS) {
        printf("HITLS_CFG_SetSignature, error code: %d.\n", ret);
        goto EXIT;
    }

    HITLS_X509_Cert *caCert = NULL;
    ret = HITLS_X509_CertParseFile(TLS_PARSE_FORMAT_ASN1, CA_PATH, &caCert);
    if (ret != HITLS_SUCCESS) {
        printf("HITLS_X509_CertParseFile CA_PATH failed, error code: %d\n", ret);
        goto EXIT;
    }

    HITLS_X509_Cert *chainCert = NULL;
    ret = HITLS_X509_CertParseFile(TLS_PARSE_FORMAT_ASN1, CHAIN_PATH, &chainCert);
    if (ret != HITLS_SUCCESS) {
        printf("HITLS_X509_CertParseFile CHAIN_PATH failed, error code: %d\n", ret);
        goto EXIT;
    }

    ret = HITLS_CFG_AddCertToStore(config, caCert, TLS_CERT_STORE_TYPE_DEFAULT, true);
    if (ret != HITLS_SUCCESS) {
        printf("HITLS_CFG_AddCertToStore failed, error code: %d\n", ret);
        goto EXIT;
    }

    ret = HITLS_CFG_AddCertToStore(config, chainCert, TLS_CERT_STORE_TYPE_DEFAULT, true);
    if (ret != HITLS_SUCCESS) {
        printf("HITLS_CFG_AddCertToStore failed, error code: %d\n", ret);
        goto EXIT;
    }

    ret = HITLS_CFG_LoadCertFile(config, EE_PATH, TLS_PARSE_FORMAT_ASN1);
    if (ret != HITLS_SUCCESS){
        printf("HITLS_CFG_LoadCertFile failed, error code: %d\n", ret);
        PrintLastError();
        goto EXIT;
    }

    ret = HITLS_CFG_LoadKeyFile(config, PRIV_PATH, TLS_PARSE_FORMAT_ASN1);
    if (ret != HITLS_SUCCESS) {
        printf("HITLS_CFG_LoadKeyFile failed, error code: %d\n", ret);
        goto EXIT;
    }

    int fd = socket(AF_INET, SOCK_STREAM, 0);
     if (fd == -1) {
        printf("Create socket failed.\n");
        goto EXIT;
    }
    int option = 1;
    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option)) < 0) {
        printf("setsockopt SO_REUSEADDR failed.\n");
        goto EXIT;
    }

    struct sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(12345);
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    if (bind(fd, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) != 0) {
        printf("bind failed.\n");
        goto EXIT;
    }
    if (listen(fd, 5) != 0) {
        printf("listen socket fail\n");
        goto EXIT;
    }

    struct sockaddr_in clientAddr;
    unsigned int len = sizeof(struct sockaddr_in);
    int infd = accept(fd, (struct sockaddr *)&clientAddr, &len);
    if (infd < 0) {
        printf("accept failed.\n");
        goto EXIT;
    }

    HITLS_Ctx *ctx = HITLS_New(config);
    if (ctx == NULL) {
        printf("HITLS_New failed.\n");
        goto EXIT;
    }

    BSL_UIO *uio = BSL_UIO_New(BSL_UIO_TcpMethod());
    if (uio == NULL) {
        printf("BSL_UIO_New failed.\n");
        goto EXIT;
    }

    ret = BSL_UIO_Ctrl(uio, BSL_UIO_SET_FD, (int32_t)sizeof(fd), &infd);
    if (ret != HITLS_SUCCESS) {
        BSL_UIO_Free(uio);
        printf("BSL_UIO_SET_FD failed, error code: %d\n", ret);
        goto EXIT;
    }

    ret = HITLS_SetUio(ctx, uio);
    if (ret != HITLS_SUCCESS) {
        BSL_UIO_Free(uio);
        printf("HITLS_SetUio failed, error code: %d.\n", ret);
        goto EXIT;
    }

    ret = HITLS_Accept(ctx);
    if (ret != HITLS_SUCCESS) {
        printf("HITLS_Accept failed, error code: %d\n", ret);
        PrintLastError();
        goto EXIT;
    }

    uint8_t readBuf[HTTP_BUF_MAXLEN + 1] = {0};
    uint32_t readLen = 0;
    ret = HITLS_Read(ctx, readBuf, HTTP_BUF_MAXLEN, &readLen);
    if (ret != HITLS_SUCCESS) {
        printf("HITLS_Read failed, error code: %d.\n", ret);
        goto EXIT;
    }
    printf("get from client size:%u :%s", readLen, readBuf);

    const uint8_t sndBuf[] = "Hi, this is server\n";
    uint32_t writeLen = 0;
    ret = HITLS_Write(ctx, sndBuf, sizeof(sndBuf), &writeLen);
    if (ret != HITLS_SUCCESS) {
        printf("HITLS_Write failed, error code: %d\n", ret);
        goto EXIT;
    }

EXIT:
    HITLS_Close(ctx);
    HITLS_Free(ctx);
    close(fd);
    close(infd);
    HITLS_X509_CertFree(caCert);
    HITLS_X509_CertFree(chainCert);
    BSL_UIO_Free(uio);
    BSL_ERR_DeInit();
    CRYPT_EAL_RandDeinit();
    HITLS_CertMethodDeinit();
    HITLS_CFG_FreeConfig(config);
    CRYPT_EAL_RandDeinitEx(libCtx);
    if (libCtx != NULL) {
        CRYPT_EAL_LibCtxFree(libCtx);
    }
}

void TLS13_SignClient()
{
    BSL_SAL_CallBack_Ctrl(BSL_SAL_MEM_MALLOC, StdMalloc);
    BSL_SAL_CallBack_Ctrl(BSL_SAL_MEM_FREE, StdFree);
    BSL_ERR_Init();
    CRYPT_EAL_RandInit(CRYPT_RAND_SHA256, NULL, NULL, NULL, 0);
    CRYPT_EAL_ProviderRandInitCtx(NULL, CRYPT_RAND_SHA256, "provider=default", NULL, 0, NULL);

    CRYPT_EAL_LibCtx *libCtx = NULL;
    CRYPT_EAL_ProvMgrCtx *providerMgr = NULL;
    HITLS_Config *config = NULL;
    int32_t ret = CRYPT_SUCCESS;

    ret = CRYPT_EAL_Init(CRYPT_EAL_INIT_CPU);
    if (ret != CRYPT_SUCCESS) {
        printf("CRYPT_EAL_LibCtxNew failed, error code: %d.\n", ret);
        goto EXIT;
    }

    libCtx = CRYPT_EAL_LibCtxNew();
    if (libCtx == NULL) {
        printf("CRYPT_EAL_LibCtxNew failed.\n");
        goto EXIT;
    }

    ret = CRYPT_EAL_ProviderSetLoadPath(libCtx, ".");
    if (ret != CRYPT_SUCCESS) {
        printf("CRYPT_EAL_ProviderSetLoadPath failed, error code: %d.\n", ret);
        goto EXIT;
    }

    ret = CRYPT_EAL_ProviderLoad(libCtx, BSL_SAL_LIB_FMT_LIBSO, "tls_provider", NULL, &providerMgr);
    if (ret != CRYPT_SUCCESS || providerMgr == NULL) {
        printf("CRYPT_EAL_ProviderLoad failed, error code: %d.\n", ret);
        goto EXIT;
    }

    ret = CRYPT_EAL_ProviderLoad(libCtx, BSL_SAL_LIB_FMT_OFF, "default", NULL, NULL);
    if (ret != CRYPT_SUCCESS) {
        printf("CRYPT_EAL_ProviderLoad failed, error code: %d.\n", ret);
        goto EXIT;
    }

    ret = CRYPT_EAL_ProviderRandInitCtx(libCtx, CRYPT_RAND_SHA256, "provider=tls_provider", NULL, 0, NULL);
    if (ret != CRYPT_SUCCESS) {
        printf("CRYPT_EAL_ProviderRandInitCtx failed, error code: %d.\n", ret);
        goto EXIT;
    }

    config = HITLS_CFG_ProviderNewTLS13Config(libCtx, NULL);
    if (config == NULL) {
        printf("HITLS_CFG_ProviderNewTLS13Config failed.\n");
        goto EXIT;
    }

    HITLS_CFG_SetCheckKeyUsage(config, false);
    HITLS_CFG_SetSecurityLevel(config, HITLS_SECURITY_LEVEL_ZERO);
    HITLS_CFG_SetReadAhead(config, 1);
    HITLS_CFG_SetFlightTransmitSwitch(config, false);

    uint16_t signScheme = 23333;
    ret = HITLS_CFG_SetSignature(config, &signScheme, 1);
    if (ret != HITLS_SUCCESS) {
        printf("HITLS_CFG_SetSignature, error code: %d.\n", ret);
        goto EXIT;
    }

    HITLS_X509_Cert *caCert = NULL;
    ret = HITLS_X509_CertParseFile(TLS_PARSE_FORMAT_ASN1, CA_PATH, &caCert);
    if (ret != HITLS_SUCCESS) {
        printf("HITLS_X509_CertParseFile CA_PATH failed, error code: %d\n", ret);
        goto EXIT;
    }

    HITLS_X509_Cert *chainCert = NULL;
    ret = HITLS_X509_CertParseFile(TLS_PARSE_FORMAT_ASN1, CHAIN_PATH, &chainCert);
    if (ret != HITLS_SUCCESS) {
        printf("HITLS_X509_CertParseFile CHAIN_PATH failed, error code: %d\n", ret);
        goto EXIT;
    }

    ret = HITLS_CFG_AddCertToStore(config, caCert, TLS_CERT_STORE_TYPE_DEFAULT, true);
    if (ret != HITLS_SUCCESS) {
        printf("HITLS_CFG_AddCertToStore failed, error code: %d\n", ret);
        goto EXIT;
    }

    ret = HITLS_CFG_AddCertToStore(config, chainCert, TLS_CERT_STORE_TYPE_DEFAULT, true);
    if (ret != HITLS_SUCCESS) {
        printf("HITLS_CFG_AddCertToStore failed, error code: %d\n", ret);
        goto EXIT;
    }

    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd == -1) {
        printf("Create socket failed.\n");
        goto EXIT;
    }
    int option = 1;
    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option)) < 0) {
        close(fd);
        printf("setsockopt SO_REUSEADDR failed.\n");
        goto EXIT;
    }

    struct sockaddr_in serverAddr;
    (void)memset_s(&serverAddr, sizeof(serverAddr), 0, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(12345);
    serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    if (connect(fd, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) != 0) {
        printf("connect failed.\n");
        goto EXIT;
    }

    HITLS_Ctx *ctx = HITLS_New(config);
    if (ctx == NULL) {
        printf("HITLS_New failed.\n");
        goto EXIT;
    }

    BSL_UIO *uio = BSL_UIO_New(BSL_UIO_TcpMethod());
    if (uio == NULL) {
        printf("BSL_UIO_New failed.\n");
        goto EXIT;
    }

    ret = BSL_UIO_Ctrl(uio, BSL_UIO_SET_FD, (int32_t)sizeof(fd), &fd);
    if (ret != HITLS_SUCCESS) {
        BSL_UIO_Free(uio);
        printf("BSL_UIO_SET_FD failed, error code: %d.\n", ret);
        goto EXIT;
    }

    ret = HITLS_SetUio(ctx, uio);
    if (ret != HITLS_SUCCESS) {
        BSL_UIO_Free(uio);
        printf("HITLS_SetUio failed, error code: %d.\n", ret);
        goto EXIT;
    }

    ret = HITLS_Connect(ctx);
    if (ret != HITLS_SUCCESS) {
        printf("HITLS_Connect failed, error code: %d.\n", ret);
        PrintLastError();
        goto EXIT;
    }

    const uint8_t sndBuf[] = "Hi, this is client\n";
    uint32_t writeLen = 0;
    ret = HITLS_Write(ctx, sndBuf, sizeof(sndBuf), &writeLen);
    if (ret != HITLS_SUCCESS) {
        printf("HITLS_Write failed, error code: %d\n", ret);
        goto EXIT;
    }

    uint8_t readBuf[HTTP_BUF_MAXLEN + 1] = {0};
    uint32_t readLen = 0;
    ret = HITLS_Read(ctx, readBuf, HTTP_BUF_MAXLEN, &readLen);
    if (ret != HITLS_SUCCESS) {
        printf("HITLS_Read failed, error code: %d.\n", ret);
        goto EXIT;
    }
    printf("get from server size:%u :%s", readLen, readBuf);

EXIT:
    HITLS_Close(ctx);
    HITLS_Free(ctx);
    close(fd);
    HITLS_X509_CertFree(caCert);
    HITLS_X509_CertFree(chainCert);
    BSL_UIO_Free(uio);
    BSL_ERR_DeInit();
    CRYPT_EAL_RandDeinit();
    HITLS_CertMethodDeinit();
    HITLS_CFG_FreeConfig(config);
    CRYPT_EAL_RandDeinitEx(libCtx);
    if (libCtx != NULL) {
        CRYPT_EAL_LibCtxFree(libCtx);
    }
}

void Test_TLS13_SignConn()
{
    pid_t pid = fork();

    if (pid < 0) {
        printf("Fork failed.");
    }

    if (pid == 0) {
        sleep(1);
        TLS13_SignClient();
    } else {
        TLS13_SignServer();
        wait(NULL);
    }
}

int main()
{
    Test_TLS13_SignConn();
    return 0;
}